﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Project1
{
	/// <summary>
	/// The console application
	/// </summary>
	class Program
	{
		/// <summary>
		/// The entry point to the console application.
		/// </summary>
		/// <param name="args">The command line arguments.</param>
		static void Main(string[] args)
		{
			Console.WriteLine("************************************");
			Console.WriteLine("* Search algorithm evaluator v 0.1 *");
			Console.WriteLine("* By Eiríkur and Grímur            *");
			Console.WriteLine("************************************");
			//
			// No command line arguments?
			//
			if (args.Length == 0)
			{
				//
				// Display the help
				//
				ShowHelp();
			}
			//
			// Should we enter statistics mode?
			//
			else if (args.Length > 1 && args[0] == "stats")
			{
				int number;
				if (!int.TryParse(args[1], out number))
				{
					ShowHelp();
					return;
				}
				GatherStatistics(number);
			}
			else
			{
				//
				// Trial mode it is. 
				// Get the number of the search algorithm to use.
				//
				int number;
				if (!int.TryParse(args[0], out number) || number < 0 || number > 4)
				{
					ShowHelp();
					return;
				}
				//
				// Test the search algorihtm
				//
				RunTest(GetSearchImplementation(number));
			}
#if DEBUG
			Console.ReadLine();
#endif
		}

		private static void ShowHelp()
		{
			Console.WriteLine("This program can be run in two modes; stats mode or trial mode.");
			Console.WriteLine("To run in stats mode use the following command:" );
			Console.WriteLine(Assembly.GetExecutingAssembly().GetName().Name + " stats <number of runs>");
			Console.WriteLine(Environment.NewLine + "To run in trial mode use the following command:");
			Console.WriteLine(Assembly.GetExecutingAssembly().GetName().Name + " <search algorithm>");
			Console.WriteLine("where ");
			Console.WriteLine("1: Breadth-First");
			Console.WriteLine("2: Depth-first");
			Console.WriteLine("3: Iterative deepening depth-first");
			Console.WriteLine("4: A-star");
		}

		private static AbstractSearch GetSearchImplementation(int number)
		{
			switch (number)
			{ 
				case 1:
					return new BreadthFirstSearch();
				case 2:
					return new DepthFirstSearch();
				case 3:
					return new IterativeDeepeningDepthFirstSearch(100);
				case 4:
					return new AStarSearch() {Heuristic = new ManhattanDistanceHeuristic()};
			}
			return null;
		}

		private static void RunTest(AbstractSearch search)
		{
			//
			// Get a list of search problems to test this algorithm on
			//
			List<IProblem> problems = GetProblems();
			foreach (IProblem problem in problems)
			{
				System.Console.WriteLine("--------------------------------------------------------------------------------");
				System.Console.WriteLine("Problem: " + Environment.NewLine + problem.ToString() + Environment.NewLine);
				//
				// Perform the search
				//
				State goalState = search.Search(problem);
				//
				// Did we find a solution?
				//
				if (goalState != null)
				{
					Console.WriteLine("Solution: " + Environment.NewLine + Environment.NewLine + problem.ToString(goalState.GetKeys()) + Environment.NewLine);
					Console.WriteLine("Path length: " + goalState.PathLength);
				}
				else
				{
					Console.WriteLine("No solution found");
				}
				//
				// Show performance metrics
				//
				Console.WriteLine("Search type: " + search.GetType().ToString() + Environment.NewLine);
				Console.WriteLine("Expanded states: " + search.ExpandedStates);
				Console.WriteLine("Maximum tree-size: " + search.MaximumTreeSize);
				Console.WriteLine("Search time: " + search.SearchTimeInMilliseconds + " (ms.)");
			}
		}

		private static void GatherStatistics(int rounds)
		{
			//
			// Collect all the search algorithms into a list
			//
			List<AbstractSearch> searchAlgorithms = new List<AbstractSearch>();
			searchAlgorithms.Add(new BreadthFirstSearch());
			searchAlgorithms.Add(new DepthFirstSearch());
			searchAlgorithms.Add(new IterativeDeepeningDepthFirstSearch(26));
			AStarSearch aStarSearch = new AStarSearch();
			aStarSearch.Heuristic = new ManhattanDistanceHeuristic();
			searchAlgorithms.Add(aStarSearch);
			//
			// Iterate through the list of search algorithms
			//
			foreach (AbstractSearch searchAlgorithm in searchAlgorithms)
			{
				//
				// Iterate through the search problems
				//
				Console.WriteLine(Environment.NewLine + "Search algorithm: " + searchAlgorithm.GetType().Name);
				foreach (IProblem problem in GetProblems())
				{
					Console.WriteLine("Problem " + (problem as GridEnvironment2D).Width + "x" + (problem as GridEnvironment2D).Height);
					//
					// HACK: Don't use iterative deepending DFS on large problems (unless you're extremely bored)
					//
					if ((problem as GridEnvironment2D).Height > 15 && searchAlgorithm is IterativeDeepeningDepthFirstSearch)
					{
						Console.WriteLine("Skipping this problem for Iterative Deepening DFS. Too big :(");
						continue;
					}
					//
					// Run the search algorithm N times
					//
					for (int index = 0; index < rounds; index++)
					{
						State goalState = searchAlgorithm.Search(problem);
						if (index == 0)
						{
							int pathLength = -1;
							if (goalState != null)
							{
								pathLength = goalState.PathLength;
							}
							Console.WriteLine("Path length={0}; Expanded states={1};Max tree size={2}", 
								pathLength, 
								searchAlgorithm.ExpandedStates, 
								searchAlgorithm.MaximumTreeSize);
						}
						Console.WriteLine("Search time (ms): " + searchAlgorithm.SearchTimeInMilliseconds);
					}
					Console.WriteLine("");
				}
			}
		}

		/// <summary>
		/// Gets a list of search problems.
		/// </summary>
		/// <returns>A list of search problems</returns>
		static List<IProblem> GetProblems()
		{
			List<IProblem> problems = new List<IProblem>();

			#region 3x3 grid
			List<Point2D> obstacles = new List<Point2D>();
			obstacles.Add(new Point2D(1, 1));
			obstacles.Add(new Point2D(1, 2));
			GridEnvironment2D gridEnvironment1 = new GridEnvironment2D(3, 3, new Point2D(0, 0), new Point2D(2, 2), obstacles);
			problems.Add(gridEnvironment1);
			#endregion

			#region 5x5 grid
			GridEnvironment2D gridEnvironment2 = new GridEnvironment2D(5, 5, new Point2D(2, 1), new Point2D(4, 2), new List<Point2D>() { new Point2D(2, 3), new Point2D(3, 0), new Point2D(3, 1), new Point2D(3, 2), new Point2D(3, 3) });
			problems.Add(gridEnvironment2);
			#endregion

			#region 12x12 grid
			List<Point2D> obstacles3 = new List<Point2D>();
			for (int y3 = 0; y3 < 11; ++y3)
			{
				obstacles3.Add(new Point2D(y3, 4));
			}
			obstacles3.Add(new Point2D(5, 5));
			obstacles3.Add(new Point2D(5, 6));
			obstacles3.Add(new Point2D(5, 7));

			GridEnvironment2D gridEnvironment3 = new GridEnvironment2D(12, 12, new Point2D(2, 2), new Point2D(3, 5), obstacles3);
			problems.Add(gridEnvironment3);
			#endregion

			#region 25x25 grid
			List<Point2D> obstacles4 = new List<Point2D>();
			for (int x4 = 3; x4 <= 20; ++x4)
			{
				Point2D upper = new Point2D(x4, 3);
				Point2D lower = new Point2D(x4, 21);
				Point2D right = new Point2D(18, x4);
				obstacles4.Add(upper);
				obstacles4.Add(lower);
				obstacles4.Add(right);
			}
			GridEnvironment2D gridEnvironment4 = new GridEnvironment2D(25, 25, new Point2D(0, 12), new Point2D(22, 14), obstacles4);
			problems.Add(gridEnvironment4);
			#endregion

			return problems;
		}
	}
}
